﻿#include "stdafx.h"
#include "MeetingDemo.h"
#include "TClientDlg.h"
#include "afxdialogex.h"
#include "asio_declare.h"
#include "AsioUtility.h"
#include "TPMKVMap.h"
#include "tpmmessageblock.h"
#include "TOptionDlg.h"
#include "request_respond.h"
#include "TPMMeetingClient.h"
#include "TPMChat.h"
#include "io.h"

namespace
{
	CRichEditCtrl* createRichEditCtrl(DWORD dwStyle, CRect rect, CWnd *pParent, UINT id = 1)
	{
		CRichEditCtrl *p = new CRichEditCtrl;
		p->Create(dwStyle, rect, pParent, id);
		p->ShowWindow(SW_SHOW);
		return p;
	}
}

IMPLEMENT_DYNAMIC(TClientDlg, CDialog)

TClientDlg::TClientDlg( const std::string &hostip, const std::string &port, CWnd* pParent /*=NULL*/)
	: CDialog(TClientDlg::IDD, pParent), m_pRichMsg(NULL), 
	m_pRichSend(NULL), m_shostIp(hostip), m_sport(port), m_ip(0), m_sendIpLst(0) { }

TClientDlg::~TClientDlg()
{
	if (m_pRichMsg)
		delete m_pRichMsg;
	if (m_pRichSend)
		delete m_pRichSend;
	m_pRichMsg = m_pRichSend = NULL;
	if (m_pKVMap)
		delete m_pKVMap;
}

void TClientDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialog::DoDataExchange(pDX);
	DDX_Control(pDX, IDC_LST_USER, m_lstUsers);
}

BEGIN_MESSAGE_MAP(TClientDlg, CDialog)
	ON_BN_CLICKED(IDC_BTN_SEND, &TClientDlg::OnBnClickedBtnSend)
	ON_WM_CTLCOLOR()
	ON_WM_CLOSE()
	ON_BN_CLICKED(IDC_BTN_OL_LIST, &TClientDlg::OnBnClickedBtnOlList)
	ON_NOTIFY(LVN_ITEMCHANGED, IDC_LST_USER, &TClientDlg::OnItemchangedLstUser)
	ON_BN_CLICKED(IDC_BTN_SEND_FILE, &TClientDlg::OnBnClickedBtnSendFile)
	ON_BN_CLICKED(IDC_BTN_CLOSE_SERVICE, &TClientDlg::OnBnClickedBtnCloseService)
END_MESSAGE_MAP()


BOOL TClientDlg::OnInitDialog()
{
	CDialog::OnInitDialog();

	IPs ips;
	Utility::getHostIP(ips);
	if (!ips.empty())
		m_ip = ips[0];

	// 新建字体
	LOGFONT lf;
	memset(&lf,0,sizeof(LOGFONT));
	lf.lfHeight = 12;
	lf.lfWeight = FW_BOLD;
	_tcscpy(lf.lfFaceName, _T("Arial"));
	m_iplst_font.CreateFontIndirect(&lf);
	lf.lfHeight = 13;
	lf.lfWeight = FW_NORMAL;
	_tcscpy(lf.lfFaceName, _T("Microsoft YaHei"));
	m_msg_font.CreateFontIndirect(&lf);

	CRect rMsg, rSend, rIpLst;
	DWORD dwStyle = ES_AUTOHSCROLL | ES_AUTOVSCROLL | ES_READONLY | ES_MULTILINE | WS_BORDER | WS_HSCROLL | WS_VSCROLL;
	// 初始化 消息编辑窗
	CWnd *pSTCMsg = GetDlgItem(IDC_STC_MSG);
	pSTCMsg->GetWindowRect(rMsg);
	ScreenToClient(rMsg);
	m_pRichMsg = createRichEditCtrl(dwStyle, rMsg, this, IDC_EDT_MSG);
	m_pRichMsg->SetFont(&m_msg_font);

	// 初始化 发送编辑窗
	CWnd *pSTCSend = GetDlgItem(IDC_STC_SEND);
	pSTCSend->GetWindowRect(rSend);
	dwStyle &= ~ES_READONLY;
	ScreenToClient(rSend);
	m_pRichSend = createRichEditCtrl(dwStyle, rSend, this, IDC_EDT_SEND);

	// 设置 在线用户列表 
	ListView_SetExtendedListViewStyle(m_lstUsers.GetSafeHwnd(),
		m_lstUsers.GetExStyle()
		| LVS_EX_CHECKBOXES
		| LVS_EX_FULLROWSELECT
		| LVS_EX_GRIDLINES);
	CRect rcClient;
	GetClientRect(rcClient);
	m_lstUsers.InsertColumn(0, _T("select"), LVCFMT_CENTER, rcClient.Width());

	m_pRichSend->SetFocus();
	
	// 新建客户端
	std::string sDbPath = Utility::getModulePathA();
	sDbPath += "\\meet.db";
	TPMMeetingClient *pClient = new TPMMeetingClient;
	pClient->create(GetSafeHwnd(),
		Utility::ip_ulong2string(m_ip),
		m_shostIp,
		m_sport,
		sDbPath,
		m_pKVMap);
	return TRUE;
}

void TClientDlg::OnBnClickedBtnSend()
{
	CString tmp(_T(""));
	m_pRichSend->GetWindowText(tmp);
	if (tmp.IsEmpty())
		return;
	CString fullStr(_T(""));
	fullStr.Format(_T("%s : %s"), Utility::getHostName().c_str(), tmp);
	m_pRichSend->SetSel(0, -1);
	m_pRichSend->Clear();

	// 构造数据库 id
	TIDLive tid;
	tid.create_id();

	// 构造请求信息
	asio_request::_request st;
	if (m_sendIpLst.empty()) // 广播
	{
		MSGFLAG::set_srv_broadcast(st.flag);
		st.ips.clear();
	}
	else
	{
		MSGFLAG::set_srv_send(st.flag);
		st.ips.assign(m_sendIpLst.begin(), m_sendIpLst.end());
		st.ips.push_back(m_ip);
	}
	st.blk.BeginSegment(TPM_CLT_TEXT);
	st.blk.writeDWORD(m_ip);
	st.blk.writeString((LPCTSTR)fullStr);
	st.blk.EndSegment();
	request_send_msg request;
	request.make_request(&st);
	m_pKVMap->setAt(tid, request.get_request());

}

BOOL TClientDlg::PreTranslateMessage(MSG* pMsg)
{
	if (WM_KEYFIRST <= pMsg->message && pMsg->message <= WM_KEYLAST)
	{
		if(pMsg->wParam == VK_RETURN)
		{
			HWND hWnd=::GetFocus();
			int iID=::GetDlgCtrlID(hWnd);
			if (iID == IDC_EDT_SEND)
				OnBnClickedBtnSend();
		}
	}

	if (pMsg->message == WM_MEETING_ACCEPT_MSG)
	{
		// 读取数据库
		TIDLive *tid = (TIDLive*)pMsg->wParam;
		TPMMessageBlock blk;
		m_pKVMap->getAt(*tid, blk);
		Utility::delete_ptr<TIDLive>(tid);

		respond_ptr ptr = Utility::createRespondClass(blk.GetFlag());
		asio_respond::_respond st;
		ptr->analy_respond(blk, st);
		CString str(_T(""));

		if (MSGFLAG::is_srv_offline_user(blk.GetFlag()) ||	// 上下线
			MSGFLAG::is_srv_online_user(blk.GetFlag()))
		{
			bool bOnline = MSGFLAG::is_srv_online_user(blk.GetFlag());
			str.Format(_T("用户 : %s %s"), Utility::ip_ulong2wstring(st.ips[0]).c_str(), bOnline ? _T("上线") : _T("下线"));
			if (bOnline)
				InsertItemToBack(st.ips[0]);
			else
				RemoveItem(st.ips[0]);
			AppendMsg2Wnd(str);
		}
		else if (MSGFLAG::is_srv_get_online_user(blk.GetFlag()))	// 在线人数
		{
			m_lstUsers.DeleteAllItems();
			str.Format(_T("当前在线用户 %d 名 :"), st.ips.size());
			for (size_t i = 0; i < st.ips.size(); ++i)
			{
				InsertItemToBack(st.ips[i]);
				str += _T("\n\t");
				str += Utility::ip_ulong2wstring(st.ips[i]).c_str();
			}
			AppendMsg2Wnd(str);
		}
		else if (MSGFLAG::is_srv_send(blk.GetFlag()) ||		// 普通消息
			MSGFLAG::is_srv_broadcast(blk.GetFlag()))
		{
			DWORD dw(0), sendIP(0);
			TCHAR *psz = nullptr;
			if (MSGFLAG::is_clt_file(st.blk.GetFlag())) // 文件传送
			{
				/*
				注意:
					如果接收失败,则一定要通知服务关闭,否则服务将会一直运行!
					如果接收成功,则不必如此.
				*/
				size_t flen(0);
				st.blk.readDWORD(&dw);
				st.blk.readDWORD(&dw);
				std::wstring ip(L""), port(L""), pszfile(L"");
				st.blk.readStringW(ip);			// 得到 host
				st.blk.readStringW(port);		// 得到 port
				st.blk.readStringW(pszfile);	// 得到 文件名
				st.blk.readBytes((byte*)&flen, sizeof(size_t));// 得到 文件长度

				// 注意,这里没有考虑文件重名问题,故 m_acceptFiles 用的是 std::map
				m_acceptFiles.insert(std::make_pair(pszfile.c_str(), flen));

				std::wstring ext = Utility::ExtractFileSuffixW(pszfile);
				CFileDialog dlg(false, ext.c_str(), pszfile.c_str());	// 得到保存路径
				do 
				{
					// 获得接收路径
					if (IDOK != dlg.DoModal())
						break;
					CString path = dlg.GetPathName();
					// 开始接收
					AppendMsg2Wnd(_T("开始接收文件..."));
					bool result = Utility::AcceptFileFromClient(
							Utility::wstring2string(ip.c_str()),
							Utility::wstring2string(port.c_str()),
							Utility::wstring2string(path), GetSafeHwnd());
					if (!result)
						break;

					goto EXIT;
				} while (false);

				// 通知服务关闭
				Utility::CloseSendFileServer(Utility::wstring2string(ip.c_str()),
							Utility::wstring2string(port.c_str()));
			}
			else
			{
				st.blk.readDWORD(&dw);
				st.blk.readDWORD(&dw);
				st.blk.readDWORD(&sendIP);
				st.blk.readString(psz);
				AppendMsg2Wnd(psz);
			}
		}
	}
	else if (pMsg->message == WM_MEETING_ACCEPT_FILE)		// 接收文件长度 时况
	{
		static int i = 0;
		size_t flen(0);
		CString fname = Utility::string2wstring((char*)pMsg->wParam).c_str();
		Utility::delete_arr<char>((char*)pMsg->wParam);

		if (m_acceptFiles.find(fname) != m_acceptFiles.end())
			flen = m_acceptFiles[fname];
		else
			goto EXIT;

		size_t accept_len = pMsg->lParam;
		CString str(_T(""));

		if (accept_len != flen)
		{
			str.Format(_T("%s 已接收 : %.1f%%"), fname, ((double)accept_len/flen)*100);
			++i;
		}
		else
		{
			str.Format(_T("%s 已传输完毕!"), fname);
			m_acceptFiles.erase(fname);
		}
		int count = m_pRichMsg->GetLineCount();
		long start = m_pRichMsg->LineIndex(count-1);
		m_pRichMsg->SetSel(start, -1);
		m_pRichMsg->ReplaceSel(str);
	}
EXIT:
	return CDialog::PreTranslateMessage(pMsg);
}

void TClientDlg::AppendMsg2Wnd( const CString &str )
{
	m_pRichMsg->SetSel(0, -1);
	CString stmp = m_pRichMsg->GetSelText();
	if (!stmp.IsEmpty())
	{
		m_pRichMsg->SetSel(-1, -1);
		m_pRichMsg->ReplaceSel(_T("\n"));
	}
	m_pRichMsg->SetSel(-1, -1);
	m_pRichMsg->ReplaceSel(str);

	m_pRichMsg->PostMessage(WM_VSCROLL, SB_BOTTOM, 0);
}

void TClientDlg::OnBnClickedBtnOlList()
{
	TIDLive tid;
	tid.create_id();
	request_ol_list ql;
	asio_request::_request req;
	MSGFLAG::set_srv_get_online_user(req.flag);
	req.ips.push_back(m_ip);
	ql.make_request(&req);
	m_pKVMap->setAt(tid, ql.get_request());
}


BOOL TClientDlg::DestroyWindow()
{
	Utility::delete_ptr<TPMKVMap>(m_pKVMap);
	Utility::delete_ptr<CRichEditCtrl>(m_pRichMsg);
	Utility::delete_ptr<CRichEditCtrl>(m_pRichSend);
	return CDialog::DestroyWindow();
}


void TClientDlg::OnCancel()
{
	CDialog::OnCancel();
	DestroyWindow();
}


void TClientDlg::OnItemchangedLstUser(NMHDR *pNMHDR, LRESULT *pResult)
{
	LPNMHEADER phdr = reinterpret_cast<LPNMHEADER>(pNMHDR);
	LPNMITEMACTIVATE pNMLV = (LPNMITEMACTIVATE)pNMHDR;
	if((pNMLV->uOldState & INDEXTOSTATEIMAGEMASK(1)) /* 旧状态 : unchecked */ 
		&& (pNMLV->uNewState & INDEXTOSTATEIMAGEMASK(2)) /* 新状态 : checked */ 
		) // 选中，将选中IP保存
	{ 
		m_sendIpLst.push_back(m_lstUsers.GetItemData(pNMLV->iItem));
	} 
	else if((pNMLV->uOldState & INDEXTOSTATEIMAGEMASK(2)) /* 旧状态 : checked */ 
		&& (pNMLV->uNewState & INDEXTOSTATEIMAGEMASK(1)) /* 新状态 : unchecked */ 
		) // 取消选中，将IP删除
	{ 
		m_sendIpLst.remove(m_lstUsers.GetItemData(pNMLV->iItem));
	} 
	*pResult = 0;
}

void TClientDlg::InsertItemToBack( const DWORD &ip )
{
	int idx = m_lstUsers.GetItemCount();
	m_lstUsers.InsertItem(idx, Utility::ip_ulong2wstring(ip).c_str());
	m_lstUsers.SetItemData(idx, ip);
}

void TClientDlg::RemoveItem( const DWORD &ip )
{
	int i = 0;
	for (; i < m_lstUsers.GetItemCount(); ++i)
	{
		if (ip == m_lstUsers.GetItemData(i))
			break;
	}
	m_lstUsers.DeleteItem(i);
}

void TClientDlg::OnBnClickedBtnSendFile()
{
	// 选中一个文件
	CFileDialog dlg(TRUE);
	CString sPath(_T(""));
	if (dlg.DoModal() == IDOK)
		sPath = dlg.GetPathName();
	else
		return;

	struct _stati64 st;
	_tstati64(sPath, &st);
	if (st.st_size >= TWO_GB)// 大于2G不支持
	{
		AfxMessageBox(_T("文件不应超过 2GB!"));
		return;
	}


	// 获得文件名
	CString sName(_T(""));
	sName = Utility::ExtractFileNameW((LPCTSTR)sPath).c_str();

	// 获得端口号
	TOptionDlg odlg(false);
	if (IDOK != odlg.DoModal())
		return;

	/*
	注意:
		这时只编写了一对一传送文件的DOMO!
		如果想要实现一对多传送文件功能,请按以下步骤:
		1.为每一个接"收端"新建一个"发送服务",并保证其端口号唯一
		2.将保存有不同端口的"接收信息"发送给对应的 "接收端"
	*/

	if (m_sendIpLst.size() != 1)
	{
		AfxMessageBox(_T("请选择一个接受用户!"));
		return;
	}

	// 建立传送服务
	Utility::SendFiletoClient(
		Utility::wstring2string(odlg.m_sPort),
		Utility::wstring2string(sPath));

	/* 发一条消息给接收方, 通知其 发送服务器 IP, port, 文件名, 接收方IP列表 */
	MsgOfSendFile(
		sName,
		(size_t)st.st_size,
		Utility::ip_ulong2wstring(m_ip).c_str(),
		odlg.m_sPort,
		m_sendIpLst);
}

void TClientDlg::MsgOfSendFile( const CString &sName, const size_t flen, const CString &host, const CString &port, IPLst &lst )
{
	TIDLive tid;
	tid.create_id();

	asio_request::_request st;
	MSGFLAG::set_srv_send(st.flag);
	// 注意! 这里不要把发送文件方的IP加入列表,因为发送方不需要接收文件
	st.ips.assign(lst.begin(), lst.end());

	st.blk.BeginSegment(TPM_CLT_FILE);
	st.blk.writeString(host);	// 从哪里发来的文件,即 文件传送服务IP
	st.blk.writeString(port);	// 文件传送服务端口号
	st.blk.writeString(sName);	// 要传送的文件名
	st.blk.writeBytes((byte*)&flen, sizeof(size_t));	// 要传送的文件长度
	st.blk.EndSegment();
	request_send_msg request;
	request.make_request(&st);
	m_pKVMap->setAt(tid, request.get_request());
}

void TClientDlg::OnBnClickedBtnCloseService()
{
	TIDLive id;
	id.create_id();
	MSGBLK blk;
	blk.BeginSegment(TPM_CLT_MEETEND);
	blk.EndSegment();
	m_pKVMap->setAt(id, blk);
}
